define('m/view', function(require, exports, module) {

  var __utils = require('m/utils');
  var Events = require('m/events');

  var View = function(options) {
    this.cid = __utils.uniqueId('view');
    options = options || {};
    __utils.extend(this, __utils.pick(options, viewOptions));
    this._ensureElement();
    this.initialize.apply(this, arguments);
    this.delegateEvents();
  };

  // Cached regex to split keys for `delegate`.
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

  // List of view options to be merged as properties.
  var viewOptions = ['data', 'options', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

  // Set up all inheritable **Mobird.View** properties and methods.
  __utils.extend(View.prototype, Events, {

    // The default `tagName` of a View's element is `"div"`.
    tagName: 'div',

    // jQuery delegate for element lookup, scoped to DOM elements within the
    // current view. This should be preferred to global lookups where possible.
    $: function(selector) {
      return this.$el.find(selector);
    },

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function() {},

    // **render** is the core function that your view should override, in order
    // to populate its element (`this.el`), with the appropriate HTML. The
    // convention is for **render** to always return `this`.
    render: function() {
      return this;
    },

    // Remove this view by taking the element out of the DOM, and removing any
    // applicable Mobird.Events listeners.
    remove: function() {
      this.$el.remove();
      this.stopListening();
      return this;
    },

    // Change the view's element (`this.el` property), including event
    // re-delegation.
    setElement: function(element, delegate) {
      if (this.$el) {
        this.undelegateEvents();
      }
      this.$el = element instanceof Mobird.$ ? element : Mobird.$(element);
      this.el = this.$el[0];
      if (delegate !== false) {
        this.delegateEvents();
      }
      return this;
    },

    // Set callbacks, where `this.events` is a hash of
    //
    // *{"event selector": "callback"}*
    //
    //     {
    //       'mousedown .title':  'edit',
    //       'click .button':     'save',
    //       'click .open':       function(e) { ... }
    //     }
    //
    // pairs. Callbacks will be bound to the view, with `this` set properly.
    // Uses event delegation for efficiency.
    // Omitting the selector binds the event to `this.el`.
    // This only works for delegate-able events: not `focus`, `blur`, and
    // not `change`, `submit`, and `reset` in Internet Explorer.
    delegateEvents: function(events) {
      if (!(events || (events = __utils.result(this, 'events')))) {
        return this;
      }
      this.undelegateEvents();
      for (var key in events) {
        var method = events[key];
        if (!__utils.isFunction(method)) {
          method = this[events[key]];
        }
        if (!method) {
          continue;
        }

        var match = key.match(delegateEventSplitter);
        var eventName = match[1],
          selector = match[2];
        method = __utils.bind(method, this);
        eventName += '.delegateEvents' + this.cid;
        if (selector === '') {
          this.$el.on(eventName, method);
        } else {
          this.$el.on(eventName, selector, method);
        }
      }
      return this;
    },

    // Clears all callbacks previously bound to the view with `delegateEvents`.
    // You usually don't need to use this, but may wish to if you have multiple
    // Mobird views attached to the same DOM element.
    undelegateEvents: function() {
      this.$el.off('.delegateEvents' + this.cid);
      return this;
    },

    // Ensure that the View has a DOM element to render into.
    // If `this.el` is a string, pass it through `$()`, take the first
    // matching element, and re-assign it to `el`. Otherwise, create
    // an element from the `id`, `className` and `tagName` properties.
    _ensureElement: function() {
      if (!this.el) {
        var attrs = __utils.extend({}, __utils.result(this, 'attributes'));
        if (this.id) {
          attrs.id = __utils.result(this, 'id');
        }
        if (this.className) {
          attrs['class'] = __utils.result(this, 'className');
        }
        var $el = Mobird.$('<' + __utils.result(this, 'tagName') + '>').attr(attrs);
        this.setElement($el, false);
      } else {
        this.setElement(__utils.result(this, 'el'), false);
      }
    }

  });

  module.exports = View;

});

Mobird.View = require('m/view');
Mobird.View.extend = Mobird.inherit;
